﻿using JESAI.Redis.Enums;
using JESAI.Redis.Event;
using JESAI.Redis.Exceptions;
using JESAI.Redis.Helpers;
using JESAI.Redis.Infos;
using JESAI.Redis.Internal.Models;
using JESAI.Redis.Messages;
using System;
using System.Buffers;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace JESAI.Redis.Internal.Status
{
    internal static class RedisClientConnectionStateManager
    {
        private static readonly ConcurrentDictionary<string, RedisClientConnectionState> _connectionStates = new ConcurrentDictionary<string, RedisClientConnectionState>();

        private static readonly ConcurrentDictionary<string, TcpClient> _tcpClients = new ConcurrentDictionary<string, TcpClient>();
        /// <summary>
        /// ping消息
        /// </summary>
        private static readonly byte[] Ping = $"{RespMessage.ArrayString}1\r\n{RespMessage.BulkStrings}{4}\r\nPing\r\n".ToBytes();

        public static async Task InitAsync(IEnumerable<string> connectionStrings)
        {
            foreach (string connectionString in connectionStrings)
            {
                var hostSplits = connectionString.Split(':');
                string host = hostSplits[0];
                int port = int.Parse(hostSplits[1]);
                string key = $"{host}:{port}";
                if (!_connectionStates.ContainsKey(key))
                {
                    var state = new RedisClientConnectionState(host, port);
                    _connectionStates.TryAdd(key, state);
                }

                foreach (var connectionState in _connectionStates)
                {
                    await HealCheckCoreAsync(connectionState);
                }

                //开启后台服务
                new Thread(HealCheckAsync)
                {
                    Name = nameof(HealCheckAsync),
                    IsBackground = true
                }.Start();

                new Thread(InValidHealCheckAsync)
                {
                    IsBackground = true,
                    Name = nameof(InValidHealCheckAsync),
                }.Start();
            }
        }

        /// <summary>
        /// 健康检查
        /// </summary>
        private static async void HealCheckAsync()
        {
            //30s 检查一次
            var timer = new Timer(async _ =>
            {
                //遍历有效的
                foreach (var connectionState in _connectionStates.Where(a => a.Value.ConnectState == ConnectStateEnum.Valid))
                {
                    await HealCheckCoreAsync(connectionState);
                }
            }, null, 0, 30000);
        }

        /// <summary>
        /// 失效连接检查
        /// </summary>
        private static async void InValidHealCheckAsync()
        {
            //3分钟 检查一次 todo 配置化时间
            var timer = new Timer(async _ =>
            {
                //检查失效的
                foreach (var connectionState in _connectionStates.Where(a => a.Value.ConnectState == ConnectStateEnum.InValid))
                {
                    //存在连接没有释放的需要先释放  SetInValid 会造成需要此操作
                    if (_tcpClients.TryRemove(connectionState.Key, out var tcp))
                    {
                        //资源释放
                        tcp.Dispose();
                        tcp = null;
                    }

                    await HealCheckCoreAsync(connectionState);
                }
            }, null, 0, 180000);
        }

        private static async Task HealCheckCoreAsync(KeyValuePair<string, RedisClientConnectionState> connectionState)
        {
            try
            {
                //获取或者创建tcp客户端
                if (!_tcpClients.TryGetValue(connectionState.Key, out var client))
                {
                    client = new TcpClient();
                    client.NoDelay = true;
                    client.ReceiveTimeout = 3000;

                    client.Client.Blocking = false;

                    await client.ConnectAsync(connectionState.Value.Host, connectionState.Value.Port);
                    _tcpClients.TryAdd(connectionState.Key, client);
                }

                var scoket = client.Client;
#if Net8
                await scoket.SendAsync(Ping);
#else
                await scoket.SendAsync(Ping, SocketFlags.None);
#endif

                using (var momery = MemoryPool<byte>.Shared.Rent(512))
                {
#if Net8
                    var buffer = await scoket.ReceiveAsync(momery.Memory);
#else
                    var buffer = await scoket.ReceiveAsync(momery.Memory, SocketFlags.None);
#endif
                    if (buffer == 0)
                    {
                        connectionState.Value.SetInValid("no response from server!");
                        return;
                    }
                }

                connectionState.Value.SetValid();
            }
            catch (Exception ex)
            {
                connectionState.Value.SetInValid(ex.Message);
            }
            finally
            {
                if (connectionState.Value.ConnectState == ConnectStateEnum.InValid && _tcpClients.TryRemove(connectionState.Key, out var client))
                {
                    //释放资源
                    client.Dispose();
                    client = null;
                }
            }
        }

        public static (string key, RedisHost redisHost) Get([CallerMemberName] string mem = "", [CallerFilePath] string path = "", [CallerLineNumber] int line = 0)
        {
            var info = _connectionStates.Where(a => a.Value.ConnectState == ConnectStateEnum.Valid).OrderBy(a => Guid.NewGuid())
                .Select(a => new
                {
                    a.Key,
                    a.Value.Host,
                    a.Value.Port
                }).FirstOrDefault();

            if (info == null)
            {
                EventBus.SelectRedisClientError("", 0, null, nameof(NotFindConnectionException));
                throw new NotFindConnectionException("not find valid redis client!");
            }

            return (info.Key, new RedisHost(info.Host, info.Port));
        }

        /// <summary>
        /// 设置无效
        /// </summary>
        /// <param name="key"></param>
        public static void SetInValid(string key)
        {
            if (!_connectionStates.TryGetValue(key, out var connectionState)) return;
            connectionState.SetInValid("conn error");
        }
    }
}
